#pragma once
#include "Util.hpp"
#include <mutex>

class users_table
{
public:
    users_table(
        const std::string &user,
        const std::string &passwd)
        : _sql(user, passwd)
    {
        _sql.init();
    }

    /**
     * @brief 插入用户信息到数据库。
     *
     * @param user Json::Value对象，包含用户信息。
     *
     * @return 成功插入返回true，否则返回false。
     *
     * @details 此函数将用户信息(user)插入到数据库表中。若用户名已存在，则插入失败并返回false。
     *          若用户信息成功插入，还会在玩家记录表中插入与用户关联的记录，并返回true。
     */
    bool insert(Json::Value &user);

    /**
     * @brief 根据用户名查询用户信息并填充Json::Value对象。
     *
     * @param user_name 要查询的用户名。
     * @param player_records 用于保存查询结果的Json::Value对象。
     *
     * @return 查询到用户返回true，否则返回false。
     *
     * @details 此函数根据给定的用户名(user_name)从数据库中查询用户信息，将查询结果填充到player_records的Json::Value对象中。
     *          如果查询到多个匹配用户，或者查询过程中出现错误，将返回false。
     *          查询结果将包括user_id、username、current_score、highest_score、total_wins、total_games_played字段。
     */
    bool select_by_name(const std::string &user_name, Json::Value &player_records);

    /**
     * @brief 根据用户ID查询用户信息并填充Json::Value对象。
     *
     * @param user_id 要查询的用户ID。
     * @param user 用于保存查询结果的Json::Value对象。
     *
     * @return 查询成功返回true，否则返回false。
     *
     * @details 此函数根据给定的用户ID(user_id)从数据库中查询用户信息，将查询结果填充到user的Json::Value对象中。
     *          如果查询到多个匹配用户，或者查询过程中出现错误，将返回false。
     *          查询结果将包括user_id、username、current_score、highest_score、total_wins、total_games_played字段。
     */
    bool select_by_id(uint32_t user_id, Json::Value &user);

    /**
     * @brief 用户登录验证。
     *
     * @param login_user 包含用户登录信息的Json::Value对象。
     *
     * @return 登录验证成功返回true，否则返回false。
     *
     * @details 此函数根据给定的用户登录信息(login_user)从数据库中验证用户是否存在且密码正确。
     *          如果验证通过，将查询到的用户信息填充到login_user的Json::Value对象中。
     *          查询结果将包括user_id、username、current_score、highest_score、total_wins、total_games_played字段。
     *          如果用户信息不唯一或者查询过程中出现错误，将返回false。
     */
    bool login(Json::Value &login_user);

    /**
     * @brief 玩家胜利记录更新。
     *
     * @param user_id 玩家的用户ID。
     *
     * @return 更新成功返回true，否则返回false。
     *
     * @details 此函数用于更新玩家的胜利记录。根据给定的用户ID(user_id)，将玩家的当前分数(current_score)增加30点，
     *          更新最高分数(highest_score)为当前分数和最高分数中的较大值，胜利总次数(total_wins)增加1，
     *          玩家游戏总次数(total_games_played)增加1。
     *          如果更新过程中出现错误，将返回false，否则返回true。
     */
    bool win(uint32_t user_id);

    /**
     * @brief 玩家失败记录更新。
     *
     * @param user_id 玩家的用户ID。
     *
     * @return 更新成功返回true，否则返回false。
     *
     * @details 此函数用于更新玩家的失败记录。根据给定的用户ID(user_id)，将玩家的当前分数(current_score)减少30点，
     *          玩家游戏总次数(total_games_played)增加1。
     *          如果更新过程中出现错误，将返回false，否则返回true。
     */
    bool lose(uint32_t user_id);

private:
    Util::mysql _sql;
    std::mutex _mutex;
};

bool users_table::insert(Json::Value &user)
{
    char insert[512] = "";
    Json::Value user_records;

    // 确保用户不重复
    if (select_by_name(user["username"].asString(), user_records))
    {
        LOG(NORMAL, "用户名重复");
        return false;
    }
    if (user["username"].isNull() || user["password"].isNull())
    {
        LOG(NORMAL, "账号密码为空");
        return false;
    }
    if (user["email"].isNull())
    {
        sprintf(insert, "insert into users (username, password) values ('%s', '%s')", user["username"].asCString(), user["password"].asCString());
    }
    else
    {
        sprintf(insert, "insert into users (username, password, email) values ('%s', '%s', '%s')", user["username"].asCString(), user["password"].asCString(), user["email"].asCString());
    }

    LOG(DEBUG, "insert(): %s", insert);

    if (!_sql.query(insert))
    {
        LOG(DEBUG, "insert() execl error");
        return false;
    }
    else
    {
        LOG(DEBUG, "insert() execl success");
    }

    // 获取用户, 方便插入用户分数
    if (!select_by_name(user["username"].asString(), user_records))
    {
        LOG(NORMAL, "用户不存在");
        return false;
    }
    char buffer[512];
    sprintf(buffer, "insert into player_records (user_id) values (%u)", user_records["user_id"].asUInt());
    LOG(DEBUG, "insert(): %s", buffer);

    if (!_sql.query(buffer))
    {
        LOG(DEBUG, "insert() execl error");
        return false;
    }
    else
    {
        LOG(DEBUG, "insert() execl success");
        return true;
    }
}

bool users_table::select_by_name(const std::string &user_name, Json::Value &player_records)
{
    char query[512] = "";
    sprintf(query, "select u.user_id, p.current_score, p.highest_score, p.total_wins, p.total_games_played \
          from users u left join player_records p on u.user_id=p.user_id where u.username='%s'",
            user_name.c_str());

    LOG(DEBUG, "select_by_name(): %s", query);

    MYSQL_RES *res = nullptr;
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!_sql.query(query))
        {
            LOG(DEBUG, "select_by_name() execl error");
        }
        else
        {
            LOG(DEBUG, "select_by_name() execl success");
        }

        res = mysql_store_result(_sql.getSQL());
        if (res == nullptr)
        {
            LOG(NORMAL, "No User info!");
            return false;
        }
    }
    int row_num = mysql_num_rows(res);
    if (row_num < 1)
    {
        return false;
    }

    // 将找到的数据存储
    MYSQL_ROW row = mysql_fetch_row(res);
    if (row != nullptr)
    {
        if (row[0] != nullptr)
        {
            player_records["user_id"] = std::stoul(row[0]);
        }
        player_records["username"] = user_name;
        if (row[1] != nullptr)
        {
            player_records["current_score"] = std::stoul(row[1]);
            player_records["highest_score"] = std::stoul(row[2]);
            player_records["total_wins"] = std::stoul(row[3]);
            player_records["total_games_played"] = std::stoul(row[4]);
        }
        mysql_free_result(res);
    }
    return true;
}

bool users_table::select_by_id(uint32_t user_id, Json::Value &user)
{
    char query[512] = "";
    sprintf(query, "select u.username, p.current_score, p.highest_score, p.total_wins, p.total_games_played \
          from users u left join player_records p on u.user_id=p.user_id where u.user_id=%d",
            user_id);

    MYSQL_RES *res = nullptr;
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!_sql.query(query))
        {
            LOG(DEBUG, "select_by_id() execl error");
        }
        else
        {
            LOG(DEBUG, "select_by_id() execl success");
        }

        res = mysql_store_result(_sql.getSQL());
        if (res == nullptr)
        {
            LOG(NORMAL, "No User info!");
            return false;
        }
    }
    int row_num = mysql_num_rows(res);
    if (row_num < 1)
    {
        return false;
    }

    MYSQL_ROW row = mysql_fetch_row(res);
    if (row != nullptr)
    {
        LOG(DEBUG, "mysql_fetch_row in...");
        user["user_id"] = user_id;
        user["username"] = row[0];
        if (row[1] != nullptr)
        {
            user["current_score"] = std::stoul(row[1]);
            user["highest_score"] = std::stoul(row[2]);
            user["total_wins"] = std::stoul(row[3]);
            user["total_games_played"] = std::stoul(row[4]);
        }
        mysql_free_result(res);
    }
    return true;
}

bool users_table::login(Json::Value &login_user)
{
    char query[512] = "";
    sprintf(query, "select p.user_id, u.username, p.current_score, p.highest_score, p.total_wins, p.total_games_played \
          from users u left join player_records p on u.user_id=p.user_id where u.username='%s' and u.password='%s'",
            login_user["username"].asCString(), login_user["password"].asCString());

    MYSQL_RES *res = nullptr;
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!_sql.query(query))
        {
            LOG(DEBUG, "login() execl error");
        }
        else
        {
            LOG(DEBUG, "login() execl success");
        }

        res = mysql_store_result(_sql.getSQL());
        if (res == nullptr)
        {
            LOG(NORMAL, "No User info!");
            return false;
        }
    }

    int row_num = mysql_num_rows(res);
    if (row_num > 1)
    {
        LOG(WARNING, "用户信息不唯一");
        return false;
    }

    MYSQL_ROW row = mysql_fetch_row(res);
    if (row != nullptr)
    {
        if (row[0] != nullptr)
        {
            login_user["user_id"] = std::stoul(row[0]);
            login_user["username"] = row[1];
        }
        if (row[2] != nullptr)
        {
            login_user["current_score"] = std::stoul(row[2]);
            login_user["highest_score"] = std::stoul(row[3]);
            login_user["total_wins"] = std::stoul(row[4]);
            login_user["total_games_played"] = std::stoul(row[5]);
        }
        mysql_free_result(res);
    }
    return true;
}

bool users_table::win(uint32_t user_id)
{
    char query[512] = "";
    sprintf(query, "update player_records \
                        set current_score=current_score+30, \
                        highest_score=GREATEST(highest_score, current_score), \
                        total_wins=total_wins+1, \
                        total_games_played=total_games_played+1\
                        where user_id=%d",
            user_id);
    if (!_sql.query(query))
    {
        LOG(DEBUG, "win() execl error");
        return false;
    }
    else
    {
        LOG(DEBUG, "win() execl success");
        return true;
    }
}

bool users_table::lose(uint32_t user_id)
{
    char query[512] = "";
    sprintf(query, "update player_records \
                        set current_score=current_score-30, \
                        total_games_played=total_games_played+1\
                        where user_id=%d",
            user_id);
    if (!_sql.query(query))
    {
        LOG(DEBUG, "lose() execl error");
        return false;
    }
    else
    {
        LOG(DEBUG, "lose() execl success");
        return true;
    }
}